package com.sobot.finance.poc.web.service;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.DateTime;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.io.resource.ResourceUtil;
import cn.hutool.core.net.URLEncodeUtil;
import cn.hutool.core.util.BooleanUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil;
import com.google.common.base.Charsets;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import com.sobot.finance.poc.web.config.SysConfig;
import com.sobot.finance.poc.web.config.TaskConfig;
import com.sobot.finance.poc.web.cons.SysConst;
import com.sobot.finance.poc.web.controller.response.GeneralRes;
import com.sobot.finance.poc.web.entity.BatchData;
import com.sobot.finance.poc.web.entity.TaskData;
import com.sobot.finance.poc.web.entity.TaskStatus;
import com.sobot.finance.poc.web.entity.Variable;
import com.sobot.finance.poc.web.util.ApiClient;
import com.sobot.finance.poc.web.util.RedisClient;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

import static com.sobot.finance.poc.web.service.TaskService.Gender.WOMAN;

/**
 * @author baiyp
 */
@Slf4j
@Service
public class TaskService {

    @Resource
    private RedisClient redisClient;

    @Resource
    private ApiClient apiClient;

    @Resource
    private SysConfig sysConfig;

    @Resource
    private TaskConfig taskConfig;

    public GeneralRes optBatchTask(TaskStatus taskStatus) {
        String operation = taskStatus.getOperation();
        if (StrUtil.equals(operation, SysConst.Opt.START.val)) {
            // TODO 启动
        } else if (StrUtil.equals(operation, SysConst.Opt.STOP.val)) {
            // TODO 暂停
        } else if (StrUtil.equals(operation, SysConst.Opt.CANCEL.val)) {
            // TODO 取消
        }

        GeneralRes generalRes = GeneralRes.success();
        Map<String, List<Map<String, String>>> failList = Maps.newHashMap();
        generalRes.setData(failList);
        return generalRes;
    }

    /**
     * 去除特殊字符
     */
    public static String handleData(String str) {
        String regEx = "[`~!@#$%^&*()+=|{}':;',\\[\\].<>/?~！@#￥%……&*（）——+|{}【】‘；：”“’。，、？]";
        Pattern p = Pattern.compile(regEx);
        Matcher m = p.matcher(str);
        return m.replaceAll("").trim();
    }

    public static void main(String[] args) {
        String time_s = "2022/03/21 09:00:00";
        String time_e = "2022/03/21 21:00:00";

        DateTime sTime = DateUtil.parse(time_s);
        DateTime eTime = DateUtil.parse(time_e);

        log.info(sTime.getTime() + "");
        log.info(eTime.getTime() + "");
    }

    public GeneralRes batchTask(BatchData batchData, boolean filter) {
        log.info("--------------------------------");
        GeneralRes generalRes = GeneralRes.success();

        String sceneCode = batchData.getSceneCode();
        String batchName = batchData.getBatchName();
        String batchNo = batchData.getBatchNo();
        String startTime = batchData.getStartTime();
        String endTime = batchData.getEndTime();
        Integer priority = batchData.getPriority();

        log.info("[startTime]: {}", startTime);
        log.info("[endTime]: {}", endTime);

        // 处理时区问题
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");
        Date sTime;
        Date eTime;
        try {
            sTime = sdf.parse(startTime);
            eTime = sdf.parse(endTime);
        } catch (ParseException e) {
            sTime = DateUtil.parse(startTime);
            eTime = DateUtil.parse(endTime);
        }

        log.info("[sTime]: {}", sTime.getTime());
        log.info("[eTime]: {}", eTime.getTime());

        // 去除特殊字符
        String name = StrUtil.sub(handleData(batchName), 0, 17);

        // 首催任务ID
        String scTaskId;
        // 复催任务ID
        String fcTaskId;
        if (hasTaskCreated(batchNo)) {
            scTaskId = getTaskId(batchNo, Boolean.TRUE);
            fcTaskId = getTaskId(batchNo, Boolean.FALSE);
        } else {
            String scTaskName = name + StrUtil.UNDERLINE + SysConst.SC;
            String fcTaskName = name + StrUtil.UNDERLINE + SysConst.FC;

            // 创建首催任务
            TaskInfo taskInfo = new TaskInfo();
            taskInfo.setTemplateId(sysConfig.getScTemplateId());
            taskInfo.setTaskName(scTaskName);
            taskInfo.setStartTime(sTime.getTime());
            taskInfo.setEndTime(eTime.getTime());
            taskInfo.setRobotNum(sysConfig.getRobotNum());
            taskInfo.setTimeType(1);
            taskInfo.setAniList(StrUtil.split(sysConfig.getAniList(), StrUtil.C_COMMA));
            taskInfo.setTelephoneRule(1);
            taskInfo.setPriority(priority);
            taskInfo.setNumberHide(0);
            taskInfo.setPushFlag(1);
            taskInfo.setPushType(2);
            taskInfo.setPushUrl(sysConfig.getPushUrl());

            // 设置拨打时间段
            taskInfo.setTaskCronList(taskConfig.getCron());
            // 设置重播
            taskInfo.setRetryFlag(sysConfig.getRetryFlag());
            taskInfo.setTaskRetryList(taskConfig.getRetry());

            // 调用API
            scTaskId = apiClient.createTask(taskInfo);
            if (StrUtil.isBlank(scTaskId)) {
                // 调用API创建任务失败
                return GeneralRes.fail();
            }

            // 创建复催任务
            taskInfo.setTemplateId(sysConfig.getFcTemplateId());
            taskInfo.setTaskName(fcTaskName);

            // 调用API
            fcTaskId = apiClient.createTask(taskInfo);
            if (StrUtil.isBlank(fcTaskId)) {
                // 调用API创建任务失败
                return GeneralRes.fail();
            }

            // 缓存任务ID
            setTaskId(batchNo, scTaskId, Boolean.TRUE);
            setTaskId(batchNo, fcTaskId, Boolean.FALSE);

            // 缓存batchNo
            setBatchNo(scTaskId, batchNo);
            setBatchNo(fcTaskId, batchNo);

            // 缓存batchName
            setBatchName(batchNo, batchName);

            // 缓存sceneCode
            setSceneCode(batchNo, sceneCode);
        }

        // 此次成功上传的拨打数据列表
        List<TelInfo> successTelInfos = Lists.newArrayList();

        // 待上传拨打数据列表
        List<TelInfo> telInfos = batchData.getTaskDataList().parallelStream().filter(taskData -> {
            boolean uploaded = telUploaded(batchNo, taskData.getTelNo());
            return BooleanUtil.negate(uploaded);
        }).map(TelInfo::of).collect(Collectors.toList());

        log.info("[telInfos]: {}", new Gson().toJson(telInfos));

        /*if (filter) {
            Gson gson = new Gson();
            String json = ResourceUtil.readUtf8Str("json/unsend.json");
            List<UnSend> unSends = gson.fromJson(json, new TypeToken<List<UnSend>>() {
            }.getType());
            List<String> dns = unSends.stream().map(UnSend::getDn).collect(Collectors.toList());
            log.info("[dns]: {}", gson.toJson(dns));
            telInfos = telInfos.stream()
                    .map(telInfo -> {
                        telInfo.setOutId(IdUtil.simpleUUID());
                        return telInfo;
                    })
                    .filter(telInfo -> dns.contains(telInfo.getTelephone()))
                    .collect(Collectors.toList());
            log.info("[telInfos]: {}", gson.toJson(telInfos));
        }*/

        List<TelInfo> scTelInfos = telInfos.stream().filter(telInfo -> {
            Map<String, String> variable = telInfo.getVariable();
            String overDays = variable.getOrDefault(SysConst.DAYS_VARIABLE_NAME, SysConst.DAYS_DEFAULT_VALUE);
            return Integer.parseInt(overDays) <= sysConfig.getLimit();
        }).collect(Collectors.toList());

        log.info("[scTelInfos]: {}", new Gson().toJson(scTelInfos));

        List<TelInfo> fcTelInfos = CollUtil.subtractToList(telInfos, scTelInfos);

        log.info("[fcTelInfos]: {}", new Gson().toJson(fcTelInfos));

        Map<String, List<TelInfo>> scParam = buildParam(scTelInfos);
        boolean scSuccess = apiClient.uploadTel(scTaskId, scParam);
        if (scSuccess) {
            setTel(batchNo, telInfos);
            successTelInfos.addAll(scTelInfos);
        }

        Map<String, List<TelInfo>> fcParam = buildParam(fcTelInfos);
        boolean fcSuccess = apiClient.uploadTel(fcTaskId, fcParam);
        if (fcSuccess) {
            setTel(batchNo, fcTelInfos);
            successTelInfos.addAll(fcTelInfos);
        }

        for (TelInfo telInfo : successTelInfos) {
            setMsg(batchNo, telInfo.getTelephone(), telInfo.getVariable());
        }

        Map<String, Object> data = Maps.newHashMap();
        data.put("batchNo", batchNo);
        data.put("batchUid", batchNo);
        List<Map<String, String>> taskInfoList = Lists.newArrayList();
        for (TelInfo telInfo : successTelInfos) {
            Map<String, String> taskInfo = Maps.newHashMap();
            taskInfo.put("taskNo", telInfo.getOutId());
            taskInfo.put("taskUid", telInfo.getOutId());
            taskInfo.put("telNo", telInfo.getTelephone());
            taskInfoList.add(taskInfo);
        }
        data.put("taskInfoList", taskInfoList);
        generalRes.setData(data);
        return generalRes;
    }

    @Data
    public static class UnSend {
        private String dn;
    }

    private Map<String, List<TelInfo>> buildParam(List<TelInfo> telInfos) {
        Map<String, List<TelInfo>> map = Maps.newHashMap();
        map.put("contactList", telInfos);
        return map;
    }

    private boolean telUploaded(String batchNo, String telNo) {
        String key = StrUtil.format(SysConst.BATCH_NO_TEL_NO_KEY_TMP, batchNo);
        boolean exist = redisClient.exist(key);
        if (exist) {
            return redisClient.existHash(key, telNo);
        }
        return false;
    }

    private void setTel(String batchNo, List<TelInfo> telInfos) {
        String key = StrUtil.format(SysConst.BATCH_NO_TEL_NO_KEY_TMP, batchNo);
        for (TelInfo telInfo : telInfos) {
            redisClient.setHash(key, telInfo.getTelephone(), telInfo.getOutId());
        }
    }

    public String getTel(String batchUid, String telNo) {
        String key = StrUtil.format(SysConst.BATCH_NO_TEL_NO_KEY_TMP, batchUid);
        return redisClient.getHash(key, telNo);
    }

    public String getTaskId(String batchNo, boolean again) {
        String key;
        if (again) {
            key = StrUtil.format(SysConst.SC_BATCH_NO_TASK_ID_KEY_TMP, batchNo);
        } else {
            key = StrUtil.format(SysConst.FC_BATCH_NO_TASK_ID_KEY_TMP, batchNo);
        }
        return redisClient.get(key);
    }

    private void setTaskId(String batchNo, String taskId, boolean again) {
        String key;
        if (again) {
            key = StrUtil.format(SysConst.SC_BATCH_NO_TASK_ID_KEY_TMP, batchNo);
        } else {
            key = StrUtil.format(SysConst.FC_BATCH_NO_TASK_ID_KEY_TMP, batchNo);
        }
        redisClient.set(key, taskId);
    }

    private boolean hasTaskCreated(String batchNo) {
        String key = StrUtil.format(SysConst.SC_BATCH_NO_TASK_ID_KEY_TMP, batchNo);
        return redisClient.exist(key);
    }

    private void setSceneCode(String batchNo, String sceneCode) {
        String key = StrUtil.format(SysConst.BATCH_NO_SCENE_CODE_KEY_TMP, batchNo);
        redisClient.set(key, sceneCode);
    }

    public String getSceneCode(String batchNo) {
        String key = StrUtil.format(SysConst.BATCH_NO_SCENE_CODE_KEY_TMP, batchNo);
        return redisClient.get(key);
    }

    private void setBatchNo(String taskId, String batchNo) {
        String key = StrUtil.format(SysConst.TASK_ID_BATCH_NO_KEY_TMP, taskId);
        redisClient.set(key, batchNo);
    }

    public String getBatchNo(String taskId) {
        String key = StrUtil.format(SysConst.TASK_ID_BATCH_NO_KEY_TMP, taskId);
        return redisClient.get(key);
    }

    private void setBatchName(String batchNo, String batchName) {
        String key = StrUtil.format(SysConst.BATCH_NO_ID_BATCH_NAME_KEY_TMP, batchNo);
        redisClient.set(key, batchName);
    }

    public String getBatchName(String batchNo) {
        String key = StrUtil.format(SysConst.BATCH_NO_ID_BATCH_NAME_KEY_TMP, batchNo);
        return redisClient.get(key);
    }

    public void setMsg(String batchNo, String telNo, Map<String, String> variableMap) {
        String key = StrUtil.format(SysConst.BATCH_NO_TEL_NO_MSG_KEY_TMP, batchNo);
        String name = variableMap.get(SysConst.NAME_VARIABLE_NAME);
        String days = variableMap.get(SysConst.DAYS_VARIABLE_NAME);
        String amt = variableMap.get(SysConst.AMT_VARIABLE_NAME);
        if (StrUtil.hasEmpty(name, days, amt)) {
            return;
        }
        String msg = StrUtil.format(sysConfig.getMsgTemplate(), name, days, amt);
        String encode = URLEncodeUtil.encode(msg, Charsets.UTF_8);
        redisClient.setHash(key, telNo, encode);
    }

    public String getMsg(String batchNo, String telNo) {
        String key = StrUtil.format(SysConst.BATCH_NO_TEL_NO_MSG_KEY_TMP, batchNo);
        return redisClient.getHash(key, telNo);
    }

   /* public static String replaceVariable(String template, Map<String, String> properties) {
        StringBuffer sb = new StringBuffer();
        // 该表达式匹配${中的数字字母下划线，英文点和$
        Matcher matcher = Pattern.compile("\\$\\{[\\w]+}").matcher(template);
        while (matcher.find()) {
            String param = matcher.group();
            String varible = param.substring(2, param.length() - 1);
            // 追加并替换变量
            Object value = properties.get(varible);
            matcher.appendReplacement(sb, value == null ? "" : String.valueOf(value));
        }
        // 复制输入序列的其余部分
        matcher.appendTail(sb);
        return sb.toString();
    }*/

    /**
     * @author baiyp
     */
    @Data
    public static class TaskInfo {
        // 话术模板ID
        private String templateId;
        // 任务名称
        private String taskName;
        // 单位：毫秒
        private Long startTime;
        // 单位：毫秒
        private Long endTime;
        // 机器⼈数量: 5个
        private Integer robotNum;
        // 任务有效期类型: 1-自定义
        private Integer timeType;
        // 外显号码
        private List<String> aniList;
        // 任务优先级, 优先级⾼则优先拨打: 1-表示低优先级 2-表示中优先级 3-表示⾼优先级
        private Integer priority;
        // 拨打时间段数组: 默认-[{"begin":"09:00","end":"20:00"}]
        private List<Map<String, String>> taskCronList;
        // 外显规则: 1-轮流
        private Integer telephoneRule;
        // 号码隐藏策略: 0-不隐藏
        private Integer numberHide;

        // ----重播设置----
        private Integer retryFlag;
        private List<Map<String, Integer>> taskRetryList;

        // ----过滤设置----
        private Integer filterFlag;
        private Integer filterRetryDay;
        private Integer filterRetryNum;

        // ----推送设置----
        // 是否开启拨打结果推送: 1-开启
        private Integer pushFlag;
        // 推送触发时机: 2-拨打结束后推送
        private Integer pushType;
        // 推送地址
        private String pushUrl;
    }

    /**
     * @author baiyp
     */
    @Data
    public static class TelInfo {
        private String outId;
        private String telephone;
        private Map<String, String> variable;

        public static TelInfo of(TaskData taskData) {
            TelInfo telInfo = new TelInfo();
            String taskNo = taskData.getTaskNo();
            String telNo = taskData.getTelNo();
            List<Variable> variables = taskData.getVariableList();
            Map<String, String> variableMap = Maps.newHashMap();
            for (Variable variable : variables) {
                variableMap.put(variable.getCode(), variable.getValue());
            }
            variableMap.compute("gender", (k, v) ->
                    StrUtil.equals(v, WOMAN.val) ? Gender.WOMAN.remark : Gender.MAN.remark);
            telInfo.setOutId(taskNo);
            telInfo.setTelephone(telNo);
            telInfo.setVariable(variableMap);

            return telInfo;
        }
    }

    /**
     * @author baiyp
     */
    public enum Gender {
        /**
         * 女士
         */
        WOMAN("20", "女士"),
        /**
         * 男士
         */
        MAN("10", "先生");

        public String val;
        public String remark;

        Gender(String val, String remark) {
            this.val = val;
            this.remark = remark;
        }
    }
}
